
#include "core/olisp_core.h"
#include "core/olisp_mem.h"
#include "utility/olisp_helpers.h"

typedef struct
{
    list_obj_vtbl_t * vtbl;

    int ref_count;

    obj_t ** vector;

    size_t length;

    size_t allocated;
} list_impl_t;

#define LIST_BUFFER_GROW_SIZE   8

static int resize_list( list_impl_t * s, size_t new_len )
{
    // allocate new elements if it is needed
    if( s->allocated < new_len )
    {
        size_t n;
        void * new_vec;

        // FIXME: use & instead of ugly while loop - determine optimal LIST_BUFFER_GROW_SIZE
        size_t new_alloc_size = s->allocated;
        while( new_alloc_size < new_len )
        {
            new_alloc_size += LIST_BUFFER_GROW_SIZE;
        }

        new_vec = lisp_realloc( s->vector, new_alloc_size * sizeof(obj_t *) );
        
        if( 0 == new_vec )
            return 0;

        // update vector
        s->vector = (obj_t **)new_vec;
        s->allocated = new_alloc_size;

        // fill with `nil' elements boundaries between old and new length
        for( n = s->length; n < new_len; ++ n )
            s->vector[n] = nil_elem;
    }

    // release thrown element
    if( new_len < s->length )
    {
        size_t n;
        for( n = new_len; n < s->length; ++ n )
        {
            obj_t * e = s->vector[n];
            RELEASE( e );
        }
    }
    
    s->length = new_len;
    return 1;
}


static int list_impl_add_ref( list_obj_t * self )
{
    list_impl_t * s = (list_impl_t *)self;
    return ++ s->ref_count;
}

static int list_impl_release( list_obj_t * self )
{
    list_impl_t * s = (list_impl_t *)self;
    int result = --s->ref_count;

    if( result != 0 )
        return result;

    resize_list( s, 0 );

    if( s->allocated > 0 )
    {
        lisp_free( s->vector );
    }

    lisp_free( s );
    return 0;
}

static size_t list_impl_length( list_obj_t * self )
{
    list_impl_t * s = (list_impl_t *)self;
    return s->length;
}

static obj_t * list_impl_get_elem( list_obj_t * self, size_t pos )
{
    list_impl_t * s = (list_impl_t *)self;
    if( pos < s->length )
    {
        obj_t * e = s->vector[pos];
        return e;
    }

    return 0;
}

static int list_impl_insert( list_obj_t * self, obj_t * elem, size_t pos, int after )
{
    list_impl_t * s = (list_impl_t *)self;
    if( pos < s->length )
    {
        size_t elem_pos = pos + (after ? 1 : 0);
        size_t new_len = s->length + 1;
        size_t n;

        if( !resize_list(s, new_len) )
            return 0;

        // move elements on their new positions
        for( n = s->length - 1; n > elem_pos; -- n )
        {
            s->vector[n] = s->vector[n - 1];
        }

        // set element
        ADDREF( elem );
        s->vector[elem_pos] = elem;

        return 1;
    }

    return 0;
}

static int list_impl_append( list_obj_t * self, obj_t * elem )
{
    list_impl_t * s = (list_impl_t *)self;
    if( !resize_list(s, s->length + 1) )
        return 0;

    // no need to release NIL elements, so
    // we do not release s->vector[s->length - 1] here
    s->vector[s->length - 1] = elem;
    ADDREF( elem );

    return 1;
}

static int list_impl_set( list_obj_t * self, size_t pos, obj_t * elem )
{
    list_impl_t * s = (list_impl_t *)self;
    if( pos < s->length )
    {
        RELEASE( s->vector[pos] );
        ADDREF( elem );
        s->vector[pos] = elem;
        return 1;
    }

    return 0;
}

static int list_impl_remove( list_obj_t * self, obj_t * elem )
{
    list_impl_t * s = (list_impl_t *)self;
    size_t n;
    for( n = 0; n < s->length; ++ n )
    {
        obj_t * e = s->vector[n];
        if( e == elem )
        {
            size_t m;
            RELEASE( e );

            for( m = n + 1; m < s->length; ++ m )
            {
                s->vector[m - 1] = s->vector[m];
            }
            
            -- s->length;
            return 1;
        }
    }

    return 0;
}

static list_obj_vtbl_t list_obj_vtbl =
{
    LISP_ELEM_LIST,
    &list_impl_add_ref,
    &list_impl_release,

    &list_impl_length,
    &list_impl_get_elem,
    &list_impl_insert,
    &list_impl_append,
    &list_impl_set,
    &list_impl_remove
};

list_obj_t * create_list( size_t initial_length )
{
    list_impl_t * s = lisp_mallocz( sizeof(list_impl_t) );
    if( !s )
        return 0;
    
    if( !resize_list(s, initial_length) )
    {
        lisp_free( s );
        return 0;
    }

    s->vtbl = &list_obj_vtbl;
    s->ref_count = 1;

    return (list_obj_t *)s;
}

